/// Kbt namespace
var Kbt = { version: "1.0" };

//
// lib part
//

/// Formats a string by replacing placeholders of kind %name% with
/// values specified in formatHash. %% stands for single %. Example:
/// Kbt.format ("line %x% is %d%%%", { x: "gg", d: 100 }) == "line gg is 100%"
/// @tparam String Format string
/// @tparam Object Hash of values, keyed by placeholder name. Entries not
/// present here but specified in placeholders of the format string will be
/// present as 'undefined' in the resulting string
Kbt.format = kbtFormat = function (formatString,formatHash)
{
	return formatString.toString ().replace (/%([^%]*)%/g,function (str,ph)
	{ return ph==""? "%" : formatHash[ph]; });
}

/// Shortcut for document.write (Kbt.format (formatString,formatHash))
document.writef = function (formatString,formatHash)
{
	document.write (Kbt.format (formatString,formatHash));
}

/// Replaces HTML special characters with corresponding entities:
/// & => &amp; " => &quot; < => &lt; > => &gt;
Kbt.htmlSpecialChars = function (str)
{
	return str.replace (/&/g,"&amp;")
		.replace (/"/g,"&quot;")
		.replace (/</g,"&lt;")
		.replace (/>/g,"&gt;");
}

/// Throw alert and Error exception with given msg. Typical usage can be like
/// this: var someObj = getObj () || Kbt.panic ("object is null or undefined!");
/// @tparam msg String Message to print and throw
Kbt.panic = kbtPanic = function (msg)
{
	alert (msg);
	throw Error (msg);
}

/// Check a condition and Kbt.panic with given message if it is false
/// @tparam flag Bool Condition to check
/// @tparam msg String Message to panic with
Kbt.assert = kbtAssert = function (flag,msg)
{
	if (!flag) Kbt.panic (msg);
}

Kbt.Func = KbtFunc = {};

/// Return a delegate - function func bound to obj. The delegate is a function,
/// calling it will forward the call to func with 'this' always set to obj,
/// no matter what activation context it is called in.
/// Object returned also has two additional members set: unbound - the function
/// unbound (namely, the 'func'), and boundTo - ref to object the function is
/// bound to. Kbt.bind itself uses these members to determine if func is already
/// bound and to optimize by returning it instead of binding it second time
/// (which would have no useful effect).
/// Kbt framework provides a shortcut: #delegate { code } or
/// #delegate (params) { code }, which stand for:
/// Kbt.bind (this,function (params) { code })
Kbt.bind = kbtBind = function (obj,func)
{
	function bound () { return func.apply (obj,arguments); }
	bound._Kbt$bound = true;
	bound.unbound = func;
	bound.boundTo = obj;
	// kind of optimization: if a function is already bound, it's no sense
	// to bind it again
	return func._Kbt$bound? func : bound;
};

/// Copy enumerable properties from obj2 to ones in obj1 with same names,
/// return obj1 (not a copy). Existing properties with same name in obj1 will
/// be overwritten.
/// @param obj1 Object to be patched
/// @param obj2 Object containing the patch
/// @return obj1 with added/replaced properties from obj2. It's not a shallow
/// copy of obj1, it's obj1 itself.
Kbt.patchObject = kbtPatchObject = function (obj1,obj2)
{
	for (var i in obj2) obj1[i] = obj2[i];
	return obj1;
};

/// Returns a copy of object obj. If obj implements MxCopyable, uses its copy
/// method to make the copy. Otherwise, returns shallow copy, with all members
/// that implement MxCopyable copied via their copy methods, and all other
/// members copied by assignment/reference.
/// @param obj Object to copy
/// @return obj's copy
Kbt.copyObject = kbtCopyObject = function (obj)
{
	var asCopyable = MxCopyable.dynamicCast (obj);
	if (asCopyable) return asCopyable.copy (obj);
	else
	{
		var result = {};
		for (var i in obj)
		{
			var member = obj[i];
			asCopyable = MxCopyable.dynamicCast (member);
			result[i] = (asCopyable && asCopyable.copy ()) || member;
		}
		return result;
	}
};

/// Invokes a sequence of methods with given names for a specified object. The
/// limitation is, all of these methods must take exactly one parameter, and
/// neither of them should not return a value of essential lifetime.
/// Convenient to invoke multiple setters for a single object.
/// @param obj Object to invoke methods for
/// @param toApply Hash of methods to invoke. Keys are method names, values are
/// parameters to pass to corresponding methods.
/// @return obj, so that the call could be chained
Kbt.multiApply = kbtMultiApply = function (obj,toApply)
{
	for (var k in toApply)
		obj[k] (toApply[k]);
	return obj;
};

KbtFunc.placeholder = function ()
{
	throw new Error ("Kbt.Func.placeholder - unimplemented placeholder");
};

KbtFunc.virtual = function (func)
{
	return { _Kbt$func: func, _Kbt$virtual: true };
};

function _kbt$pureVirtual ()
{
	throw new Error("Kbt.Func.virtual - pure virtual called");
};


//
// mixin main support
//

Kbt.Mixin = KbtMixin = {}; // mixins namespace
// mxId => mixin
KbtMixin._Kbt$regMixins = [];
// mtdId => { mx: mixin, mtdName: name:string, isVirtual: bool }
KbtMixin._Kbt$regMethods = [];
// mtdName => mxId => mtdId
KbtMixin._Kbt$revRegMethods = {};

/// Enable mixin usage interface for object obj
KbtMixin.enable = function (obj)
{
	if (obj._Kbt$mxRoot==undefined)
	{
		obj._Kbt$mxRoot = obj;
		obj._Kbt$methods = {}; // mtdId => bound function
		obj._Kbt$gateways = {}; // mxId => { _Kbt$mxRoot: obj, [mtdName: bound func] }
		// also serves as a flag that mxId is enabled
		KbtMixin._Kbt$mxEnable (obj,MxBase);
	}
};

KbtMixin._Kbt$mxEnable = function (obj,pMxRef)
{
	kbtAssert (pMxRef._Kbt$mxId!=undefined,
		"MxBase::mxEnable (pMxRef): pMxRef is not a mixin");
	if (!obj._Kbt$gateways[pMxRef._Kbt$mxId])
	{
		// obj does not have pMxRef enabled yet

		// do a trick: obj will be new pMxRef instance's prototype. It will
		// allow us to set up method shortcuts in obj only but still have
		// them visible from this pMxRef instance
		var prevProto = pMxRef.prototype;
		pMxRef.prototype = obj;
		try {
			result = new pMxRef ();
			return result;
		}
		finally // as ctor has done its job, return prev prototype back
		{ pMxRef.prototype = prevProto; }
	}
	else
		return obj;
};

var MxBase = undefined; // temporary, will be replaced as MxBase is defined

// helper for mixin instance constructor/overrider
function _kbt$applyDefinitions (methods,defines)
{
	var mxRoot = this._Kbt$mxRoot;
	for (var i in defines)
	{
		let define = defines[i],
			mtd = KbtMixin._Kbt$regMethods[i],
			mtdName = mtd.mtdName,
			prevDef = mxRoot[mtdName];
		let newMtd;
		if (define===KbtFunc.placeholder)
		{
			// placeholder encountered
			let me = this,mtdId = i;
			newMtd = {
				implement: function (func)
				{
					var prevMtd = mxRoot[mtdName];
					var newMtd = mxRoot._Kbt$methods[mtdId] =
						kbtPatchObject (kbtBind (me,func));
					// shortcut is ours - update shortcut
					if (prevMtd==undefined || prevMtd._Kbt$methodOf==mtd.mx)
						mxRoot[mtdName] = newMtd;
				},
				_Kbt$methodOf: mtd.mx
			};
		}
		else
			newMtd = kbtBind (this,define);
		mxRoot._Kbt$methods[i] = newMtd;

		// add named shortcut
		if (prevDef==undefined || prevDef._Kbt$methodOf===undefined)
		{
			// no shortcut or existing shortcut is unrelated - define
			mxRoot[mtdName] = kbtPatchObject
				(newMtd,{ _Kbt$methodOf: mtd.mx });
		}
		else if (prevDef._Kbt$methodOf===mtd.mx)
		{
			// our definition is an override and there is no ambiguity yet
			mxRoot[mtdName] = kbtPatchObject
				(newMtd,{ _Kbt$methodOf: mtd.mx });
		}
		else if (prevDef._Kbt$methodOf!=false)
		{
			// our definition is not an override and introduces first
			// ambiguity
			var
				pUsing = function (pMxRef)
				{
					kbtAssert (pMxRef._Kbt$mxId!=undefined,
						"using/resolveTo (pMxRef): pMxRef is not a mixin");
					var mtdId = KbtMixin
						._Kbt$revRegMethods[mtdName][pMxRef._Kbt$mxId];
					kbtAssert (mtdId!=undefined,
						"using/resolveTo (pMxRef): method '"+mtdName+"' is not implemented in mixin #"+pMxRef._Kbt$mxId);
					return mxRoot._Kbt$methods[mtdId];
				},
				pResolveTo = function (pMxRef) // disambiguator
				{
					var func = mxRoot[mtdName].using (pMxRef);
					mxRoot[mtdName] = kbtPatchObject(
						function () { return func.apply (this,arguments) },
						// it's next argument, don't get mixed
						{
							using: pUsing,
							resolveTo: pResolveTo,
							_Kbt$methodOf: pMxRef
						});
				};
			mxRoot[mtdName] = kbtPatchObject (
			function ()
			{
				Kbt.panic ("Kbt.Mixin - ambiguous method shortcut '"+mtdName+"', use .using or .resolveTo to disambiguate");
			},
			{
				_Kbt$methodOf: false, // ambiguity marker
				/// Dynamic desambiguator. Allows to pick the implementation
				/// from specific mixin.
				using: pUsing,
				/// Freezing static desambiguator. Once you call it, this
				/// named shortcut will be bound to implementation of pMxRef.
				/// You can still use using to access other implementations
				/// or call resolveTo to readjust.
				resolveTo: pResolveTo
			});
		}
	}
}

/// Declare a mixin.
/// @return The mixin declared. Consider the object returned as a class.
KbtMixin.declare = function (enablesList,addsList,ctor)
{
	// mxId: int, identifier of the mixin in Kbt.Mixin registry
	var mxId = KbtMixin._Kbt$regMixins.length;

	// result (as ctor function)
	function mixin ()
	{
		KbtMixin.enable (this);
		var mxRoot = this._Kbt$mxRoot;
		kbtAssert (!mxRoot._Kbt$mxFrozen,"MxBase::mxEnable - can't mixin after MxBase::mxFreeze");

		// first, apply mixins from enablesList
		for (var i in enablesList)
		{
			var pMx = enablesList[i],
				pOvList;
			if (mxRoot._Kbt$gateways[pMx._Kbt$mxId])
				continue; // the mixin is already enabled
			if (pOvList = pMx.overrides)
				pMx = KbtMixin._Kbt$regMixins[pMx._Kbt$mxId];
			pMx.apply (mxRoot);
		}

		// add/redefine our own definitions/overrides
		_kbt$applyDefinitions.call (this,this._Kbt$methods,mixin._Kbt$defines);

		// apply constructor
		if (ctor!=undefined) ctor.apply (this);

		// officially acknowledge us mixed in
		mxRoot._Kbt$gateways[mixin._Kbt$mxId] = {};
		delete i;
		// end of mixin instance constructor
	}
	// fill in some extras
	mixin._Kbt$mxId = mxId;
	mixin._Kbt$ctor = ctor;
	mixin._Kbt$ownMethodsByName = {};
	mixin._Kbt$enablesMethod = function (mtdName)
	{
		if (this._Kbt$ownMethodsByName[mtdName]!=undefined)
			return this._Kbt$ownMethodsByName[mtdName];
		else
		{
			var enablesMx = this._Kbt$enables;
			for (var i in enablesMx)
			{
				var mtdId = enablesMx[i]._Kbt$enablesMethod (mtdName);
				if (mtdId!=undefined) return mtdId;
			}
			return undefined;
		}
	};

	// register the mixin
	KbtMixin._Kbt$regMixins[mxId] = mixin;

	// every mixin implicitly enables MxBase (except of MxBase itself which
	// defines itself explicitly)
	if (MxBase) enablesList.unshift (MxBase);

	// register added methods and add them to definitions:
	mixin._Kbt$defines = {}; // methods we define (our own) or redefine
	mixin._Kbt$ownMethods = [];
	// own methods
	for (let mtdName in addsList)
	{
		let mtdId = KbtMixin._Kbt$regMethods.length,
			mtd = addsList[mtdName];
		let isVirtual = false;
		if (mtd===KbtFunc.virtual)
		{
			// declaration 'mtdName: Kbt.Func.virtual'
			mtd = {
				_Kbt$func: _kbt$pureVirtual,
				_Kbt$virtual: true
			};
		}
		else if (mtd instanceof Function)
		{
			// declaration 'mtdName: function () ...'
			// or 'mtdName: Kbt.Func.placeholder
			mtd = { _Kbt$func: mtd, _Kbt$virtual: false };
		}
		else
			kbtAssert (mtd._Kbt$func!=undefined,
				"Kbt.Mixin.declare: only functions or virtual function placeholders can be in to-add list");
		// register method
		KbtMixin._Kbt$regMethods[mtdId] =
		{
			mx: mixin,
			mtdName: mtdName,
			isVirtual: mtd._Kbt$virtual
		};
		let revMtdReg = KbtMixin._Kbt$revRegMethods[mtdName]
			|| (KbtMixin._Kbt$revRegMethods[mtdName] = {});
		revMtdReg[mxId] = mtdId;
		mixin._Kbt$defines[mtdId] = mtd._Kbt$func;
		mixin._Kbt$ownMethods.push (mtdId);
		mixin._Kbt$ownMethodsByName[mtdName] = mtdId;
	}

	// mxEnables[mxId][name] = override_type
	var enables = mixin._Kbt$enables = [],
		alreadyEnabled = {}; // mxId => true, initially add self
	alreadyEnabled[mxId] = true; // initially add self (just for precaution)
	//document.write (htmlize (enablesList.toString ()));
	for (let i in enablesList)
	{
		let pMx = enablesList[i],
			pOvList;
		kbtAssert (pMx!=undefined && pMx._Kbt$mxId!=undefined,
			"Kbt.Mixin.declare (enablesList,addsList,ctor): not a mixin in enablesList");
		if (pOvList = pMx.overrides)
			pMx = KbtMixin._Kbt$regMixins[pMx._Kbt$mxId];
		kbtAssert (!alreadyEnabled[pMx._Kbt$mxId],
			"Kbt.Mixin.declare (enablesList,addsList,ctor): duplicate mixin #"+pMx._Kbt$mxId+" in enablesList");
		alreadyEnabled[pMx._Kbt$mxId] = true;
		enables.push (pMx);

		// add entries from pOvList to our defines
		if (pOvList)
		for (let mtdName in pOvList)
		{
			let mtdId = pMx._Kbt$enablesMethod (mtdName);
			kbtAssert (mtdId!=undefined,
				"Kbt.Mixin.declare (enablesList,addsList,ctor): method '"+mtdName+"' is not defined in mixin #"+pMx._Kbt$mxId+" from enableList and can't be overridden");
			kbtAssert (KbtMixin._Kbt$regMethods[mtdId].isVirtual,
				"Kbt.Mixin.declare (enablesList,addsList,ctor): method '"+mtdName+"' is not virtual in mixin #"+pMx._Kbt$mxId+" from enableList and can't be overridden");
			let pOv = pOvList[mtdName];
			mixin._Kbt$defines[mtdId] = pOv;
		}
	}

	delete alreadyEnabled;
	delete enables;

	// 'static' mixin methods:

	/// Prepare a list of methods to override. Use this call
	/// for filling to-enable list in Kbt.Mixin.declare.
	mixin.override = function (overrideList)
	{
		var result = {};
		result._Kbt$mxId = this._Kbt$mxId;
		result.overrides = overrideList; // this is also an indicator
		// that it's not the mixin itself, just its id + override list
		return result;
	};

	mixin._Kbt$identifyOverrideList = function (obj,missing)
	{
		// does the same as identifyOverrideList (see below) but in addition
		// tells if there are entries that are required but not implemented
		// in obj (fills _Kbt$incomplImpl flag) if missing is true

		// build virtual functions list by name
		var pVirtIds = {},
			pCompIds = {},
			pOptIds = {},
			reg = KbtMixin._Kbt$regMethods;
		function buildMe (pMx)
		{
			var enables = pMx._Kbt$enables,
				defines = pMx._Kbt$defines;
			// build bottom-up, so that bottommost overrides would take place
			for (var i in enables) buildMe (enables[i]);
			for (var i in defines)
			{
				var regMtd = reg[i];
				if (regMtd.isVirtual)
				if (defines[i]==_kbt$pureVirtual)
					pCompIds[regMtd.mtdName] = i;
				else
					pOptIds[regMtd.mtdName] = i;
			}
		}
		buildMe (this);
		// fill compulsory ids
		if (missing)
		{
			pVirtIds._Kbt$incomplImpl = false;
			for (var mtdName in pCompIds)
			{
				var func = obj[mtdName];
				if (func instanceof Function)
					// object has the implementation
					pVirtIds[mtdName] = kbtBind (obj,func);
				else
				{
					pVirtIds._Kbt$incomplImpl = true;
					return pVirtIds;
				}
			}
		}
		else
		for (var mtdName in pCompIds)
		{
			var func = obj[mtdName];
			if (func instanceof Function)
				// object has the implementation
				pVirtIds[mtdName] = kbtBind (obj,func);
			else
				pVirtIds[mtdName] = true;
		}
		// fill optional ids
		for (var mtdName in pOptIds)
		{
			var func = obj[mtdName];
			if (func instanceof Function)
				// object has the implementation
				pVirtIds[mtdName] = kbtBind (obj,func);
			else
				delete pVirtIds[mtdName];
		}
		return pVirtIds;
	}

	/// Identify possible implementation of this mixin in obj (obj has
	/// members of same names as virtual or pure virtual functions of this
	/// mixin, and they are functions) and return override list that can
	/// be used to override implementation of this mixin (via
	/// MxBase::mxOverride) with forwarding to implementation in obj
	mixin.identifyOverrideList = function (obj)
	{
		return this._Kbt$identifyOverrideList (obj,false);
	};

	/// Return gateway to this mixin implemented in obj if any, or return
	/// undefined if the mixin is not implemented in obj
	mixin.dynamicCast = function (obj)
	{
		if (!obj) return null; // obvious case 
		var mxRoot = obj._Kbt$mxRoot,
			gateway = mxRoot && mxRoot._Kbt$gateways[mixin._Kbt$mxId];
		if (!(mxRoot&&gateway)) return undefined;
		if (!gateway._Kbt$mxRoot)
		{
			// gateway is not yet filled
			gateway._Kbt$mxRoot = mxRoot;
			var bp = mixin._Kbt$gatewayBlueprint;
			for (var mtdName in bp)
			{
				var mtds = bp[mtdName];
				if (mtds.length>1)
				{
					// ambiguity
					var pUsing = function (pMxRef)
					{
						kbtAssert (pMxRef._Kbt$mxId!=undefined,
							"using/resolveTo (pMxRef): pMxRef is not a mixin");
						var mtdId = KbtMixin
							._Kbt$revRegMethods[mtdName][pMxRef._Kbt$mxId];
						kbtAssert (mtdId!=undefined,
							"using/resolveTo (pMxRef): method '"+mtdName+"' is not implemented in mixin #"+pMxRef._Kbt$mxId);
						return mxRoot._Kbt$methods[mtdId];
					},
					pResolveTo = function (pMxRef) // disambiguator
					{
						var func = mxRoot[mtdName].using (pMxRef);
						mxRoot[mtdName] = kbtPatchObject(
							function () { return func.apply (this,arguments) },
							// it's next argument, don't get mixed
							{
								using: pUsing,
								resolveTo: pResolveTo
							});
					};
					gateway[mtdName] = kbtPatchObj (
						function ()
						{
							Kbt.panic ("Kbt.Mixin - ambiguous method shortcut '"+mtdName+"', use .using or .resolveTo to disambiguate");
						},
						{
							using: pUsing,
							resolveTo: pResolveTo
						});
				}
				else
					gateway[mtdName] = mxRoot._Kbt$methods[mtds[0]];
			}
		}
		return gateway;
	};

	var mtdBP = mixin._Kbt$gatewayBlueprint = []; // name => [mtdId]
	var mtdUsed = {};
	function fillGatewayBlueprint (pMx)
	{
		var enables = pMx._Kbt$enables,
			defines = pMx._Kbt$defines;
		for (var i in enables) fillGatewayBlueprint (enables[i]);
		for (var i in defines)
		if (!mtdUsed[i])
		{
			mtdUsed[i] = true;
			var regName = KbtMixin._Kbt$regMethods[i].mtdName,
				list = mtdBP[regName] || (mtdBP[regName] = []);
			list.push (i);
		}
	}
	fillGatewayBlueprint (mixin);

	// finally, clean up and return what we have
	delete mxId;
	delete mtdUsed;
	delete mtdBP;
	return mixin;
};

//
// and some predefined mixins
//

/// MxBase
/// Provides basic interface for a mixin: enabling it in an object, overriding
/// virtual functons, etc. MxBase is implicitly enabled by any other mixin.
Kbt.MxBase = MxBase = KbtMixin.declare ([],
// adds
{
	/// pMxRef: Mixin
	/// If mixin pMxRef is not present in this object, construct and implant it
	/// @return gateway to pMxRef
	mxEnable: function (pMxRef)
	{
		KbtMixin._Kbt$mxEnable (this._Kbt$mxRoot,pMxRef);
	},
	/// pMxRef: Mixin
	/// overrideList: [{funcId: function}]
	/// Overrides given functions from mixin pMxRef in this object with given
	/// implementations. pMxRef must be guaranteed to be enabled in this object
	/// before you use this function.
	/// @return hash of implementations that were in effect before the override
	/// (name => un-overridden method). It's not a gateway, just a helper.
	mxOverride: function (pMxRef,overrideList)
	{
		// add entries from pOvList to our defines
		var overrides = {},prevImpl = {},
			mxRoot = this._Kbt$mxRoot,
			methods = mxRoot._Kbt$methods;
		kbtAssert (!mxRoot._Kbt$mxFrozen,"MxBase::mxOverride - can't override after MxBase::mxFreeze");
		for (let mtdName in overrideList)
		{
			let mtdId = pMxRef._Kbt$enablesMethod (mtdName);
			kbtAssert (mtdId!=undefined,
				"Kbt.Mixin.declare (enablesList,addsList,ctor): method '"+mtdName+"' is not defined in mixin #"+pMxRef._Kbt$mxId+" from enableList and can't be overridden");
			kbtAssert (KbtMixin._Kbt$regMethods[mtdId].isVirtual,
				"Kbt.Mixin.declare (enablesList,addsList,ctor): method '"+mtdName+"' is not virtual in mixin #"+pMxRef._Kbt$mxId+" from enableList and can't be overridden");
			overrides[mtdId] = overrideList[mtdName];
			prevImpl[mtdName] = methods[mtdId];
		}
		_kbt$applyDefinitions.call (mxRoot,methods,overrides);
		// override invalidates existing gateway
		mxRoot._Kbt$gateways[pMxRef._Kbt$mxId] = {};
		return prevImpl;
	},
	/// pMxRef: Mixin
	/// obj: any object
	/// Attempts to identify implementation of pMxRef in object obj (it has
	/// members with same names as all virtual functions in pMxRef and they
	/// are functions (assumed methods of obj)) and enable official
	/// implementation of pMxRef in this object that will route requests to obj.
	/// The call fails if pMxRef is already enabled in this object or if obj
	/// doesn't implement all required members as functions.
	/// @return gateway to identified implementation of pMxRef
	mxIdentify: function (pMxRef,obj)
	{
		var mxRoot = this._Kbt$mxRoot,
			methods = mxRoot._Kbt$methods;
		kbtAssert (pMxRef._Kbt$ctor==undefined,
			"MxBase::mxIdentify - mixin #"+pMxRef._Kbt$mxId+" has a constructor and can't be identified in arbitrary object");
		kbtAssert (!mxRoot._Kbt$gateways[pMxRef._Kbt$mxId],
			"MxBase::mxIdentify - mixin #"+pMxRef._Kbt$mxId+" is already enabled");
		var ovList = pMxRef._Kbt$identifyOverrideList (obj,true);
		kbtAssert (!ovList._Kbt$incomplImpl,
			"MxBase::mxIdentify - identifiable object doesn't implement all pure virtual functions");
		delete ovList._Kbt$incomplImpl;
		KbtMixin._Kbt$mxEnable (mxRoot,pMxRef);
		var overrides = {};
		for (let mtdName in ovList)
		{
			let mtdId = pMxRef._Kbt$enablesMethod (mtdName);
			kbtAssert (mtdId!=undefined,
				"MxBase::mxIdentify (pMxRef,obj): method '"+mtdName+"' is not defined in mixin #"+pMxRef._Kbt$mxId+" from enableList and can't be overridden");
			kbtAssert (KbtMixin._Kbt$regMethods[mtdId].isVirtual,
				"Kbt.Mixin.declare (enablesList,addsList,ctor): method '"+mtdName+"' is not virtual in mixin #"+pMxRef._Kbt$mxId+" from enableList and can't be overridden");
			overrides[mtdId] = ovList[mtdName];
		}
		_kbt$applyDefinitions.call (mxRoot,methods,overrides);
	},
	/// Returns original mixin implementator for a public mixin gateway. I. e.,
	/// if you obtained a gateway from an object via MxYourMixin.castFrom,
	/// you can obtain direct ref to that object back from the gateway with
	/// mxRoot.
	mxRoot: function ()
	{
		return this._Kbt$mxRoot;
	},
	/// Disables any further augmentation of the object with new mixins and
	/// overriding existing ones, also converts all methods to frozen versions
	/// that can be possibly more optimized for performance. Don't use inside
	/// mixins, use for final classes only.
	mxFreeze: function ()
	{
		var mxRoot = this._Kbt$mxRoot;
		if (!mxRoot._Kbt$mxFrozen)
		{
			mxRoot._Kbt$mxFrozen = true;
			// ...probably to do something else
		}
	}
});

/// ImxEnumerable
/// Should be implemented by classes/mixins that can serve as containers
/// providing sequential iteration through some their contents.
Kbt.ImxEnumerable = ImxEnumerable = Kbt.Mixin.declare ([],
// adds
{
	/// Return enumerator thru implementor's elements. Assumed use in form:
	/// for (var/let x in obj.elements ()) ...
	elements: Kbt.Func.virtual, // ()
	/// Return all elements currently present in implementor in an array. Uses
	/// elements to iterate thru elements.
	/// @treturn Array All elements currently present in implementor as an array
	elementsSnapshot: function ()
	{
		var result = [];
		for (var x in this.elements ()) result.push (x);
		return result;
	}
});

/// ImxEnumerableBiDi
/// Should be implemented by classes/mixins that can serve as containers
/// providing sequential iteration through some their contents and are able
/// to provide it in both directions.
Kbt.ImxEnumerableBiDi = ImxEnumerableBiDi = Kbt.Mixin.declare ([ImxEnumerable],
// adds
{
	/// Return enumerator thru implementor's elements in reverse order. Assumed
	/// use in form: for (var/let x in obj.elements ()) ...
	elementsReverse: Kbt.Func.virtual, // ()
	/// Return all elements currently present in implementor in an array, in
	/// reverse order. Uses elementsReverse to iterate thru elements.
	/// @treturn Array All elements currently present in implementor as a
	/// reversed array
	elementsReverseSnapshot: function ()
	{
		var result = [];
		for (var x in this.elementsReverse ()) result.push (x);
		return result;
	}
});

/// ImxCopyable
/// Should be implemented by classes/mixins that assume by-value use and should
/// be rather copied than passed by reference. Objects that implement
/// ImxCopyable are regarded by Kbt.copyObject as ones to be copied by value
/// (using their ImxCopyable::copy method), whenever they are copied themselves
/// or are immediate members of a copied object.
Kbt.ImxCopyable = ImxCopyable = Kbt.Mixin.declare ([],
// adds
{
	/// Return copy of the object. It's entirely up to implementation to
	/// construct what it deems to be its copy, it just has to be a new object.
	copy: Kbt.Func.virtual // ()
});

/// Class that wraps multiple exceptions stacked together. Can be thrown on
/// dispose or auto-dispose.
/// Implements: ImxEnumerable
Kbt.Exceptions = KbtExceptions = function ()
{
	KbtMixin.enable (this);
	
	var myElements = [];
	this.elements = function () // implementation of ImxEnumerable
	{
		for (var i in myElements) yield myElements[i];
	};

	/// Add an exception to the collection. If it's another Kbt.Exceptions,
	/// unroll it and append all its exceptions individually, so Kbt.Exceptions
	/// is never nested.
	this.push = function (exception)
	{
		if (exception instanceof KbtExceptions)
			// other Kbt.Exceptions - push unrolled
			for (var e in exception.elements ())
				myElements.push (e);
		else
			myElements.push (exception);
	};

	/// Remove first exception in the collection and shift it. Return undefined
	/// if the collection is empty
	this.shift = function ()
	{
		return myElements.length>0 ? myElements.shift () : undefined;
	};

	this.peek = function ()
	{
		return myElements.length>0 ? myElements[0] : undefined;
	};

	this.clear = function () { myElements.clear (); };
	this.count = function () { return myElements.length; };

	this.mxIdentify (ImxEnumerable,this);
}

/// Returns e if it's not a Kbt.Exceptions or returns first exception in the
/// collection (w/o removing it) if it is. When using Kbt extensions, always use
/// this function inside catch to get the actual current exception instead of
/// direct use of catch variable, because an exception may be (and may be not)
/// wrapped in Kbt.Exceptions.

/// A helper that you should always use in catch when using Kbt extensions. The
/// strategy is as following:
/// catch (rawEx) {
/// let e = Kbt.Exceptions.preCatch (rawEx,
///  function (e) e instanceof ExceptionClassYouWant);
/// ...e contains the exception you wanted, or the control doesn't get here...
/// }
/// @param e Exception you obtained from catch (e)
/// @tparam predicate Function One-argument function returning bool, should
/// return true if its parameter is an exception you are expecting, or false
/// otherwise
/// @return The actual exception conforming predicate if e included it wrapped
/// or unwrapped, or rethrows otherwise. If the exception was wrapped and
/// conformed predicate but there were other exceptions, drops it from the
/// collection and rethrows the rest (it conforms with multi-exception catching
/// convention assumed by Kbt).
KbtExceptions.preCatch = function (e,predicate)
{
	if (e instanceof KbtExceptions)
	{
		var ex = e.peek ();
		if (predicate (ex))
		{
			e.shift ();
			if (e.count ()>0) throw e;
			return ex;
		}
		else throw e;
	}
	else if (predicate (e)) return e;
	else
		throw e;
};

/// MxDisposable
/// Should be implemented by classes/mixins that intend to provide explicit,
/// deterministic deinitialization (destructors). Such objects can be switched
/// to disposed state by calling MxDisposable::dispose method. Also, supports
/// reference counting that allows you to share a MxDisposable implementing
/// object by several locations without fear to dispose it while it is still
/// needed.
/// You do not override dispose function directly. Instead, your class/mixin
/// constructor should set up a 'disposal agenda', a sequence of actions (which
/// can be either functions to call or objects implementing MxDisposable to
/// dispose) that will be called on dispose in LIFO order. Every action is
/// guaranteed to be called, even if any of them throw an exception. If any
/// action from disposal agenda throws an exception, these exceptions accumulate
/// and are propagated to caller of dispose, packed in Kbt.Exceptions.
/// Kbt framework provides handy syntax extensions to make use of MxDisposable
/// easier. #disposable ClassNameopt (...) { ... } is a shortcut for
/// function ClassNameopt (...) { ... } that implicitly augments the constructor
/// with enabling of MxDisposable, and also automatically triggers current
/// disposal agenda if the constructor throws an exception. Also, later in this
/// constructor, use this.#~ { code } to add an action to dispose agenda or
/// this.#~(expression_result_is_MxDisposable) to add a MxDisposable object.
/// this.#~ { ... } is a statement, while this.#~(...) is an expression that
/// returns the object added and allows to write like this:
/// var disposableMember = this.#~(new MyMxDisposable (...));.
Kbt.MxDisposable = MxDisposable = KbtMixin.declare ([],
// adds
{
	/// Perform disposal agenda. It is performed in LIFO order. Every item is
	/// guaranteed an invocation attempt, even if previous items throw an
	/// exception. If exceptions occured during dispose, they are wadded into
	/// KbtExceptions and thrown as it.
	/// Performing dispose clears the dispose agenda, so you are secured from
	/// disposing twice. Also, you are secured from recursive call (e. g., when
	/// dispose is invoked from a dispose agenda), there will be simply no
	/// effect in this case.
	/// If the object has been retained (retain), its retain counter will be
	/// decremented by 1, and dispose will only be performed when it reaches 0.
	dispose: Kbt.Func.placeholder, // ()
	/// Add a function to call or a MxDisposable implementor to dispose to
	/// dispose agenda
	/// @param funcOrDisposable, function or a disposable. Function is called
	/// in an indefinite context, so it's recommended to make it via
	/// Kbt.Func.bind (or #delegate).
	/// @return The same item you passed in funcOrDisposable. It allows syntax
	/// like var autoDisposeMember = this.markToDispose (new SomeMxDisposable ());
	/// @exceptions Error If the item is not function or a MxDisposable 
	markToDispose: Kbt.Func.placeholder, // (funcOrDisposable item) => item
	/// Increase reatin counter for the object. Object that enables MxDisposable
	/// starts with retain counter equal to 0. Calling retain will increase it
	/// by 1. If the retain counter is above 1, subsequent dispose will not
	/// do actual dispose but will just decrease counter by 1 instead.
	/// Use retain if you are going to share same MxDisposable object between
	/// several users with unpredictably varying lifetime.
	/// @return The object itself (mxRoot of the MxDisposable). It allows syntax
	/// like var sharedAutoDisposeMbr = someMxDisposable.retain ();
	retain: Kbt.Func.placeholder, // () => this
	/// Get current value of retain counter. Initial value for retain counter on
	/// creation of MxDisposable is 0.
	/// @treturn Number Current retain counter value
	retainCount: Kbt.Func.placeholder, // () => Number
	/// Returns true if the object is disposed
	/// @treturn Boolean True if the object is disposed, false otherwise
	isDisposed: Kbt.Func.placeholder // () => Boolean
},
// ctor
function ()
{
	var agenda = [],
		retainCount = 0,
		disposeInProgress = false;
	
	// dispose
	this.dispose.implement (function ()
	{
		if (disposeInProgress || --retainCount>0) return;
		disposeInProgress = true;
		var i = agenda.length,
			exs = null; // KbtExceptions
		while (--i>=0)
		try { agenda[i] (); }
		catch (e)
		{
			if (exs==null) exs = new KbtExceptions ();
			exs.push (e);
		}
		agenda.length = 0;
		if (exs!=null) throw exs;
	});

	// markToDispose implementation
	this.markToDispose.implement (function (funcOrDisposable)
	{
		var pAsDisposable;
		if (pAsDisposable = MxDisposable.dynamicCast (funcOrDisposable))
			agenda.push
				(kbtBind (pAsDisposable,function () { this.dispose (); }));
		else if (funcOrDisposable instanceof Function)
			agenda.push (funcOrDisposable);
		else throw new Error ("MxDisposable::markToDispose: item must be a parameterless function or implement MxDisposable");
		return funcOrDisposable;
	});

	// retain implementation
	this.retain.implement (function ()
	{
		++retainCount;
		return this.mxRoot ();
	});

	// retainCount implementation
	this.retainCount.implement (function ()
	{
		return retainCount;
	});

	// isDisposed implementation
	this.isDisposed.implement (function ()
	{
		return disposeInProgress && !agenda.length;
	});
});

// helper for #auto
_Kbt$AutoScope = function ()
{
	KbtMixin.enable (this);
	this.mxEnable (MxDisposable);
	this.exceptions = new KbtExceptions ();
}


/// Double-linked list that allows to iterate through its elements and manage
/// their membership explicitly by exposing access to list nodes.
/// Implements: ImxEnumerableBiDi
Kbt.List = KbtList = function ()
{
	KbtMixin.enable (this);

	var firstNode = null,lastNode = null,count = 0,revision = {};

	/// Return number of entries currently in list
	/// @treturn Number Elements count
	this.count = function () { return count; };

	/// Add an object or a node before a given node of this list
	/// @tparam nodeBefore Kbt.ListNode Node to add the entry before. Must be a
	/// node already belonging to this list or null if you want to add the entry
	/// to the end of the list
	/// @tparam nodeOrObj Kbt.ListNode|Object Object or node to add. If it's a
	/// node, it must not be currently in any list
	/// @treturn Kbt.ListNode Newly added node (nodeOrObj itself if it was a
	/// node)
	this.addBefore = function (nodeBefore,nodeOrObj)
	{
		kbtAssert (nodeBefore===null ||
			(nodeBefore instanceof KbtListNode && nodeBefore._Kbt$list===this),
			"Kbt.List.addBefore: nodeBefore must be null or a node from this list");
		if (nodeOrObj instanceof KbtListNode)
		{
			// a node
			kbtAssert (nodeOrObj._Kbt$list===null,"Kbt.ListNode already in list");
			nodeOrObj._Kbt$list = this;
			if (nodeBefore===null)
			{
				let prevLast = lastNode;
				nodeOrObj._Kbt$prev = prevLast;
				nodeOrObj._Kbt$next = null;
				if (prevLast!==null) prevLast._Kbt$next = nodeOrObj;
				else firstNode = nodeOrObj;
				lastNode = nodeOrObj;
			}
			else
			{
				let prevPrev = nodeBefore._Kbt$prev;
				nodeOrObj._Kbt$prev = prevPrev;
				nodeOrObj._Kbt$next = nodeBefore;
				nodeBefore._Kbt$prev = nodeOrObj;
				if (prevPrev!==null) prevPrev._Kbt$next = nodeOrObj;
				else firstNode = nodeOrObj;
			}
			count++;
			revision = {};
			return nodeOrObj;
		}
		else // other class of object
			this.addBefore (nodeBefore,new KbtListNode (nodeOrObj));
	};

	/// Add an object or a node after a given node of this list
	/// @tparam nodeBefore Kbt.ListNode Node to add the entry after. Must be a
	/// node already belonging to this list or null if you want to add the entry
	/// to the beginning of the list
	/// @tparam nodeOrObj Kbt.ListNode|Object Object or node to add. If it's a
	/// node, it must not be currently in any list
	/// @treturn Kbt.ListNode Newly added node (nodeOrObj itself if it was a
	/// node)
	this.addAfter = function (nodeAfter,nodeOrObj)
	{
		kbtAssert (nodeAfter===null ||
			(nodeAfter instanceof KbtListNode && nodeAfter._Kbt$list===this),
			"Kbt.List.addAfter: nodeAfter must be null or a node from this list");
		if (nodeOrObj instanceof KbtListNode)
		{
			// a node
			kbtAssert (nodeOrObj._Kbt$list===null,"Kbt.ListNode already in list");
			nodeOrObj._Kbt$list = this;
			if (nodeAfter===null)
			{
				let prevFirst = firstNode;
				nodeOrObj._Kbt$prev = null;
				nodeOrObj._Kbt$next = prevFirst;
				if (prevFirst!==null) prevFirst._Kbt$prev = nodeOrObj;
				else lastNode = nodeOrObj;
				firstNode = nodeOrObj;
			}
			else
			{
				let prevNext = nodeAfter._Kbt$next;
				nodeOrObj._Kbt$next = prevNext;
				nodeOrObj._Kbt$prev = nodeAfter;
				nodeAfter._Kbt$next = nodeOrObj;
				if (prevNext!==null) prevNext._Kbt$prev = nodeOrObj;
				else lastNode = nodeOrObj;
			}
			count++;
			revision = {};
			return nodeOrObj;
		}
		else // other class of object
			return this.addAfter (nodeAfter,new KbtListNode (nodeOrObj));
	};

	/// Add a node or an object to end of this list
	/// @tparam nodeOrObj Kbt.ListNode|Object Object or node to add. If it's a
	/// node, it must not be currently in any list
	/// @treturn Kbt.ListNode Newly added node (nodeOrObj itself if it was a
	/// node)
	this.addLast = function (nodeOrObj)
	{
		return this.addAfter (lastNode,nodeOrObj);
	};

	/// Add a node or an object to beginning of this list
	/// @tparam nodeOrObj Kbt.ListNode|Object Object or node to add. If it's a
	/// node, it must not be currently in any list
	/// @treturn Kbt.ListNode Newly added node (nodeOrObj itself if it was a
	/// node)
	this.addFirst = function (nodeOrObj)
	{
		return this.addBefore (firstNode,nodeOrObj);
	};

	/// Remove a node from the list
	/// @tparam nodeOrObj Kbt.ListNode|Object Node to remove. It must be a node
	/// currently in this list
	this.remove = function (node)
	{
		kbtAssert (node instanceof KbtListNode &&
			node._Kbt$list===this,
			"Kbt.List.remove: node must be a Kbt.ListNode currently in the list");
		var prevNext = node._Kbt$next,
			prevPrev = node._Kbt$prev;
		if (prevNext!==null) prevNext._Kbt$prev = prevPrev;
		else lastNode = prevPrev;
		node._Kbt$list = node._Kbt$next = node._Kbt$prev = null;

		if (prevPrev!==null) prevPrev._Kbt$next = prevNext;
		else firstNode = prevNext;

		count--;
		revision = {};
	};

	/// Removes first node in list (if any) and returns it (or null if list was
	/// empty)
	/// @treturn Kbt.ListNode Node previously first on list or null if the list
	/// was empty
	this.removeFirst = function ()
	{
		if (firstNode)
		{
			var result = firstNode;
			this.remove (firstNode);
			return result;
		}
		else return null;
	};

	/// Removes first node in list (if any) and returns it (or null if list was
	/// empty)
	/// @treturn Kbt.ListNode Node previously first on list or null if the list
	/// was empty
	this.removeLast = function ()
	{
		if (lastNode)
		{
			var result = lastNode;
			this.remove (lastNode);
			return result;
		}
		else return null;
	};

	/// Remove all current entries from the list
	this.clear = function ()
	{
		var next;
		for (var n=firstNode; n!==null; n=next)
		{
			n._Kbt$list = n._Kbt$prev = null;
			next = n._Kbt$next;
			n._Kbt$next = null;
		}
		firstNode = lastNode = null;
		count = 0;
		revision = {};
	};

	/// Return an iterator through the entries
	/// @return Iterator to use in for (var x in theIterator)
	this.nodes = function ()
	{
		var curRevision = revision;
		for (var n=firstNode; n!==null; n=n._Kbt$next)
		{
			kbtAssert (curRevision===revision,
				"Kbt.List.nodes: list has changed during iteration");
			yield n;
		}
	};

	/// Return list's first node, null if list is empty
	this.firstNode = function () { return firstNode; }

	/// Return list's last node, null if list is empty
	this.lastNode = function () { return lastNode; }

	this.elements = function ()
	{
		var curRevision = revision;
		for (var n=firstNode; n!==null; n=n._Kbt$next)
		{
			kbtAssert (curRevision===revision,
				"Kbt.List.nodes: list has changed during iteration");
			yield n.value;
		}
	}

	this.elementsReverse = function ()
	{
		var curRevision = revision;
		for (var n=lastNode; n!==null; n=n._Kbt$prev)
		{
			kbtAssert (curRevision===revision,
				"Kbt.List.nodes: list has changed during iteration");
			yield n.value;
		}
	}

	this.mxIdentify (ImxEnumerableBiDi,this);
	this.mxFreeze ();
}

/// Node in a Kbt.List. Does not implement any mixins.
Kbt.ListNode = KbtListNode = function (value)
{
	this._Kbt$list = null; // KbtList
	this._Kbt$next = this._Kbt$prev = null; // KbtListNode
	this.value = value;

	/// Get the list node is in or null if it's not in a list
	/// @treturn Kbt.List List this node is (null if none)
	this.list = function () { return this._Kbt$list; }

	/// Get prev node in the list
	/// @treturn Kbt.ListNode Prev node in this list (null if none)
	this.prev = function () { return this._Kbt$prev; }

	/// Get next node in the list
	/// @treturn Kbt.ListNode Next node in this list (null if none)
	this.next = function () { return this._Kbt$next; }
}

/// Returns a trigger - object that encapsulates call to multiple delegates
/// registered in a dynamically built invocation list (like C# delegates).
/// These functions are assumed to return nothing and to take same set of
/// parameters.
/// Create a trigger: var/let t = Kbt.createTrigger (); then invoke
/// it as a regular function: t (params);
/// Subscribe delegates (created with Kbt.bind or #delegate):
/// var subscription = t.add (#delegate { ... });
/// (note that subscribed item must be exactly the delegate, not a naked
/// function)
/// Subscription is a gateway to MxDisposable that you may use to unsubscribe:
/// subscription.dispose ();
Kbt.newTrigger = kbtNewTrigger = function ()
{
	kbtAssert (!(this instanceof kbtNewTrigger),
		"Kbt.Trigger - use Kbt.Trigger (), not new Kbt.Trigger () to create");

	var invList = new KbtList ();
	function doer ()
	{
		for (var f in invList.elements ())
			f.apply (this,arguments);
	}

	function Subscription (node)
	{
		Kbt.Mixin.enable (this);
		this.mxEnable (MxDisposable);
		this.markToDispose (function () { invList.remove (node); });
	}

	doer.add = function (f)
	{
		kbtAssert (f.boundTo!==undefined,
			"Kbt.newTrigger - can only subscribe a delegate (use Kbt.bind of #delegate)");

		var node = invList.addLast (f);
		return new Subscription (node);
	}

	return doer;
};

// Coroutine observer
Kbt.Observer = _Kbt$Observer =
	function (observersList,condition,isInterruptor,action)
{
	KbtMixin.enable (this);
	this.mxEnable (MxDisposable);

	this._Kbt$isInterruptor = isInterruptor;
	this._Kbt$condition = condition; // function () { let vname=msg; return expr; }
	this._Kbt$code = action;
	this._Kbt$suspendLevel = 0;
	this.suspend = function ()
	{
		this._Kbt$suspendLevel++;
		//return kbtBind (this,function () { this._Kbt$suspendLevel--; });
		return new function (me)
		{
			KbtMixin.enable (this);
			this.mxEnable (MxDisposable);
			this.markToDispose (function () { me._Kbt$suspendLevel--; });
		} (this);
	};
	this.resume = function ()
	{
		this._Kbt$suspendLevel--;
		//return kbtBind (this,function () { this._Kbt$suspendLevel++; });
		return new function (me)
		{
			KbtMixin.enable (this);
			this.mxEnable (MxDisposable);
			this.markToDispose (function () { me._Kbt$suspendLevel++; });
		} (this);
	}

	var node = observersList.addLast (this);
	this.markToDispose (function () { observersList.remove (node); });
};

_Kbt$crtMsgReenter = 'Kbt coroutine object re-entered';
_Kbt$crtMsgDisposeUnsafe = 'Kbt coroutine object disposed in unsafe state';

//
// event queue support
//

/// Implements event queue. The idea is as follows: you subscribe coroutines to
/// an event queue, then post event into the event queue, and the event is
/// offered as message to all subscribed coroutines. To optimize performance,
/// events are classified in types (therefore an object used as event must have
/// a string field named 'eventType'), and subscription of a coroutine requires
/// to specify set of event types that coroutine will be listening to. Events
/// of unlistened type are not offered to the coroutine.
/// An event may be consumed (if it triggers #expect or #[observe] of a matched
/// coroutine) or observed (if it triggers #observe). An event can be observed
/// by many observers but can only be consumed by one expector/interruptor. In
/// latter case, it's not offered to any observers/expectors remaining, but it
/// might have been handled by observers that happened to be earlier in
/// listeners queue. Since it's not possible to predict who exactly will observe
/// the event, it's recommended to separate event types by those that should
/// only be observed and those that should only be consumed, and identify these
/// classes by different eventType.
/// If the event is not consumed, it's dropped from the queue. However, you may
/// post it with postPersistent, then the event will not be dropped and will
/// keep being re-enqueued for next handling cycle until consumed. Use it in
/// case when your event must be consumed but you are not sure that a consumer
/// exists at time of you post it. (Use persistence with caution, as it may
/// litter the queue on misuse. Don't use it for events that are assumed to be
/// observed, for they will be observed on each attempt.)
/// Your event object may implement ImxDroppableEvent mixin if you want to
/// handle fact of dropping an event in some special way (to show an alert,
/// e. g.).
/// The event is 'static' if it's an object that enables MxStaticEvent mixin.
/// Static event must be posted with separate functions, postStatic and
/// postStaticPersistent, which are basically the same as post and
/// postPersistent. The difference is that same MxStaticEvent enabled object
/// can't be posted again until it's consumed or dropped. In exchange, you can
/// check its status (whether it is currently posted or not) and to withdraw it
/// from the queue manually. Event withdrawn will not be offered to any
/// listeners.
/// When the queue handling cycle is completed, the queue goes idle (expects for
/// new events). Before it does, an onIdle trigger is triggered. You can hook on
/// it to perform some actions on queue going idle.
/// That's all you have to know about event queues.
Kbt.MxEventQueue = MxEventQueue = KbtMixin.declare ([],
// adds
{
	/// Subscribe a coroutine into queue. Object to subscribe must be either one
	/// created via 'new CoroutineFunctionName ()' or one returned by #[self]
	/// from inside coroutine function. Return MxDisposable that can be disposed
	/// to cancel this subscription.
	/// Second parameter is array of strings each of which describes an event
	/// type this subscriber will listen. May be omitted (or contain of single
	/// item "*") which means that the subscriber will listen to any type of
	/// event. Using this is not recommended for common use however, for each of
	/// matching subscribers will have to be checked, and having too much of
	/// them will have negative impact on performance. To prevent it, try to
	/// specify only those event types in subscription that are really needed
	/// for its particular task, and to have as few observers subscribed for
	/// a particular event type as possible.
	subscribe: Kbt.Func.placeholder, // (Crt crt,[] evtTypes) => MxDisposable

	/// Posts an event into the queue. If the queue is idle, event is handled
	/// before post returns, otherwise (event processing is in progress) it is
	/// enqueued.
	/// Event can be any object that has "eventType" field of String type.
	/// This field is used for preliminary filtering. The event will not be
	/// offered to a subscribed coroutine unless its eventType matches one of
	/// event types specified by coroutine in subscribe.
	/// Will panic if event has MxStaticEvent mixin enabled. Events that enable
	/// MxStaticEvent must be posted using other function, postStatic.
	post: Kbt.Func.placeholder, // (event)

	/// Same as post, but an event will not be dropped from the queue until
	/// consumed.
	postPersistent: Kbt.Func.placeholder, // (event)

	/// Posts a static event into the queue. Panics if event has no
	/// MxStaticEvent mixin enabled, or if the event is already posted and not
	/// yet processed.
	postStatic: Kbt.Func.placeholder, // (MxStaticEvent event)

	/// Same as postStatic, but an event will not be dropped from the queue
	/// until consumed (or withdrawn).
	postStaticPersistent: Kbt.Func.placeholder, // (MxStaticEvent event)

	/// Returns a trigger that is triggered when the queue goes idle
	triggerOnIdle: Kbt.Func.placeholder, // () => Trigger

	/// Process accumulated events. Has no effect if re-entered (i. e., called
	/// from a coroutine). Use in main event loop to process events after you
	/// have posted them.
	processEvents: Kbt.Func.placeholder, // ()
},
function ()
{
	var queue = new KbtList (),
		handling = false,
		endMarker = new KbtListNode (),
		subsEndMarker = new KbtListNode (),
		newEvents = false,
		onIdleTrigger = kbtNewTrigger (),
		subscribersByType = // 'classId' => KbtList
		{
			// a 'check all events' list, all events will be offered to
			// listeners from it
			"*": new KbtList ()
		};

	queue._Kbt$eventQueue = this; // a secret internal var
	this.processEvents.implement (function ()
	{
		if (!handling)
		{
			handling = true;
			try
			{
				for (; newEvents;)
				{
					newEvents = false;
					queue.addLast (endMarker);
					var nowNode;
					for (nowNode=queue.firstNode (), queue.remove (nowNode);
						nowNode!=endMarker;
						nowNode=queue.firstNode (), queue.remove (nowNode))
					{
						var theEvent = nowNode.value,eventConsumed = false;
						kbtAssert (theEvent.eventType!==undefined,
							"An event must have eventType member defined");
						var ets = [theEvent.eventType, "*"];

						SUBS_ITERATE:
						for (var et in ets)
						{
							let typeToCheck = ets[et];
							// subscribersByType, pick one by one (using
							// subsEndMarker as iteration end marker), offer
							// theEvent to each one until consumed
							var subscribers = subscribersByType[typeToCheck]
								|| (subscribersByType[typeToCheck] = new KbtList ());
							subscribers.addLast (subsEndMarker);
							var nowSubsNode;
							// pick subscriber, moving him to end of queue
							// at the same time (somewhat verbose, isn't it)
							for (nowSubsNode=subscribers.firstNode (),
								subscribers.remove (nowSubsNode),
								subscribers.addLast (nowSubsNode);
								nowSubsNode!=subsEndMarker;
								nowSubsNode=subscribers.firstNode (),
								subscribers.remove (nowSubsNode),
								subscribers.addLast (nowSubsNode))
							{
								var theSubs = nowSubsNode.value;
								theSubs (theEvent); // offer event to subscriber
								if (theSubs.msgConsumed)
								{
									// event consumed, offer it to no one more.
									eventConsumed = true;
									break SUBS_ITERATE;
								}
							}
							subscribers.remove (subsEndMarker);
						}
						// remove subscriber queue end marker
						if (subsEndMarker.list ()!=null)
							subsEndMarker.list ().remove (subsEndMarker);
						if (!eventConsumed)
						{
							// event not consumed
							if (nowNode.persistent)
								// persistent event - re-enqueue
								queue.addLast (nowNode);
							else
							{
								// not persistent event - try ImxDroppableEvent
								var droppable =
									ImxDroppableEvent.dynamicCast (theEvent);
								if (droppable) droppable.runOnDrop ();
							}
						}
					}
	            }
	            // events are over, no new ones posted
	            onIdleTrigger ();
            }
            catch (e)
            {
            	// on exception, the queue is emergently cleared, and the
            	// exception is propagated to original poster
            	queue.clear ();
            	newEvents = false;
            	throw e;
            }
            finally
            {
            	handling = false;
            	if (endMarker.list ()!=null)
            		endMarker.list ().remove (endMarker);
				if (subsEndMarker.list ()!=null)
            		subsEndMarker.list ().remove (subsEndMarker);
            }
		}
	});

	this.post.implement (function (event)
	{
		kbtAssert (!MxStaticEvent.dynamicCast (event),
			"MxEventQueue: post can't be applied to MxStaticEvent, use postStatic");
		var node = queue.addLast (event);
		node.persistent = false;
		newEvents = true;
	});

	this.postPersistent.implement (function (event)
	{
		kbtAssert (!MxStaticEvent.dynamicCast (event),
			"MxEventQueue: postPersistent can't be applied to MxStaticEvent, use postStaticPersistent");
		var node = queue.addLast (event);
		node.persistent = true;
		newEvents = true;
	});

	this.postStatic.implement (function (event)
	{
		var staticEvent = MxStaticEvent.dynamicCast (event) ||
			kbtPanic ("MxEventQueue: postStatic can only be applied to MxStaticEvent");
		staticEventLink = staticEvent.mxRoot ()._Kbt$staticEventLink;
		!staticEventLink.list ()
			|| kbtPanic ("MxEventQueue: MxStaticEvent is already in a queue");
		queue.addLast (staticEventLink);
		staticEventLink.persistent = false;
		newEvents = true;
	});

	this.postStaticPersistent.implement (function (event)
	{
		var staticEvent = MxStaticEvent.dynamicCast (event) ||
			kbtPanic ("MxEventQueue: postStaticPersistent can only be applied to MxStaticEvent");
		staticEventLink = staticEvent.mxRoot ()._Kbt$staticEventLink;
		!staticEventLink.list ()
			|| kbtPanic ("MxEventQueue: MxStaticEvent is already in a queue");
		queue.addLast (staticEventLink);
		staticEventLink.persistent = true;
		newEvents = true;
	});

	this.triggerOnIdle.implement (function () { return onIdleTrigger; });

	this.subscribe.implement (function (crt,evtTypes)
	{
		var result = new function ()
		{
			KbtMixin.enable (this);
			this.mxEnable (MxDisposable);

			if (evtTypes===undefined) evtTypes = ["*"];
			var types = [];
			for (var type in evtTypes)
				types[evtTypes[type]] = new KbtListNode (crt);
			this.markToDispose (function ()
			{
				var node;
				for (var type in types)
				{
					node = types[type];
					if (node.list ()!=null) node.list ().remove (node);
				}
			});
			for (var type in types)
				(subscribersByType[type] ||
					(subscribersByType[type] = new KbtList ()))
				.addLast (types[type]);
		} ();
		newEvents = true;
		return result;
	});
});

Kbt.MxStaticEvent = MxStaticEvent = Kbt.Mixin.declare([MxDisposable],
// adds
{
	/// Returns event queue the event is posted in or null if it isn't posted
	/// (or is already processed).
	postedIn: Kbt.Func.placeholder, // () => MxEventQueue

	/// Withdraws the event from the queue immediately without processing. Event
	/// can be posted again. Ok to call if the event is not posted
	withdraw: Kbt.Func.placeholder, // ()
},
function ()
{
	// secret internal var
	var
		staticEventLink =
		this.mxRoot ()._Kbt$staticEventLink =
			new KbtListNode (this.mxRoot ());

	this.postedIn.implement (function ()
	{
		return staticEventLink.list () &&
			staticEventLink.list ()._Kbt$eventQueue; // use secret internal var
	});

	this.withdraw.implement (function ()
	{
		var list = staticEventLink.list ();
		if (list) list.remove (staticEventLink);
	});
	this.markToDispose (kbtBind (this,function () { this.withdraw (); }));
});

Kbt.ImxDroppableEvent = ImxDroppableEvent = Kbt.Mixin.declare([],
// adds
{
	/// Is invoked when event object implementing this mixin is dropped from
	/// the queue without consumption
	runOnDrop: Kbt.Func.virtual // ()
});
